/** @file */
#include "stdafx.h"
#include "PlasmaBall.h"

#include <OgreParticleSystem.h>
#include <OgreBillboardSet.h>

#include <OgreBillboard.h>
#include <OgreVector3.h>

#include <OgreSubMesh.h>
#include <OgreResourceGroupManager.h>
#include <OgreMaterialManager.h>

#include <sstream>
#include <string>
#include "BoxComponent.h"
#include "SphereComponent.h"
#include <math.h>

using namespace Ogre;


void PlasmaBall::updateLogic() {

}

PlasmaBall::PlasmaBall(Ogre::Vector3 position, bool dynamic,  float radius, unsigned int tessellation, tendrilTexture tt, 
							PlasmaBall::sphereTexture st, bool hasLight) {

	m_radius = radius;

	/* Disallowed values of tessellation are changed to appropriate ones before setting the member variable. */
	if ( tessellation < 1 ) {
		tessellation = 1;
	}
	else if (  tessellation > 15 ) {
		tessellation = 15;
	}

	m_tessellation = 16 * tessellation;
	
	/* Init m_node. This is a member of the superclass GameObject. It must be set explicitly before createMovableObject is called. */
	m_node = NULL;

	/* Set the physicsComponent. This integrates the plasma ball with the physics extension. */
	m_physicsComponent = new SphereComponent(position, m_radius, m_radius, dynamic);

	{
		bool tendrils = true;

		/* Set the tendril texture. Also disables tendrils if that option is choosen. */
		std::string tendrilTex = "";

		switch (tt) {
			case 0 : /* TENDRIL_LINE */
				tendrilTex = "Plasma_bolt.png";
				break;
			case 1 : /* TENDRIL_FORKED */
				tendrilTex = "Forked_bolt.png";
				break;
			case 2 : /* TENDRIL_NO_TENDRILS */
				tendrilTex = "";
				tendrils = false;
				break;
			default:
				tendrilTex = "Plasma_bolt.png"; //will never happen
		}

		m_tendrilTexName = tendrilTex;
		
		/* Set the tendril texture. Also disables tendrils if that option is choosen. */
		std::string sphereTex = "";

		switch (st) {
			case 0 : /* TRANSPARENT_REFLECTIVE */
				sphereTex = "PlasmaBall/BigBall";
				break;
			case 1 : /* SOLID_GRASS */
				sphereTex = "Examples/GrassFloor";
				tendrils = false;
				break;
			case 2 : /* SOLID_ROCKY */
				sphereTex = "Examples/Rocky";
				tendrils = false;
				break;
			default:
				sphereTex = "PlasmaBall/BigBall"; //will never happen
		}

		m_sphereTexName = sphereTex;

		m_hasTendrils = tendrils;
	}

	m_hasLight = hasLight;
}


PlasmaBall::~PlasmaBall(void){
	delete m_physicsComponent;
}


void PlasmaBall::createMovableObject(Ogre::SceneManager* sceneMgr, unsigned int id) {
	
	if (m_hasTendrils) {
		
		/* Create the particle system for the tendrils, and apply appropriate settings */
		{
			/* Create a unique name for the particle system by adding the id. */
			std::stringstream psOut;
			psOut << "TendrilSys" << id;
			std::string particleId = psOut.str();
		
			/* Create the particle system from a particle script */
			m_particleSystem = sceneMgr->createParticleSystem(particleId, "PlasmaArms");

			m_particleSystem->setDefaultDimensions(m_radius/8, m_radius);
			m_particleSystem->setKeepParticlesInLocalSpace(true);
		}

		/* Change the texture that is used by the particle system */
		{
			/* Create a unique name for the tendril material by adding the id. */
			std::stringstream matOut;
			matOut << "TendrilMaterial" << id;
			std::string materialId = matOut.str();

			/* Fetch the material that is used by the particle system. Make a copy of it with the unique name. */
			MaterialPtr scriptMaterial = MaterialManager::getSingletonPtr()->getByName( m_particleSystem->getMaterialName() );
			MaterialPtr tendrilMaterial = scriptMaterial->clone(materialId);
		
			/* Change the texture used. */
			Ogre::AliasTextureNamePairList aliasList;
			aliasList.insert( AliasTextureNamePairList::value_type("TendrilType", m_tendrilTexName) );

			tendrilMaterial->applyTextureAliases(aliasList);

			/* Set the newly changed material to be used by the particle system. */
			m_particleSystem->setMaterialName(materialId);
		}

		/* Add the particle system to the list of movable objects, and attach it to the scene node. */
		this->addMovableObject(m_particleSystem);
		m_node->attachObject(m_particleSystem);
	}

	if (m_hasLight) {
		Light* light = sceneMgr->createLight();
		light->setType(Light::LightTypes::LT_POINT);
		light->setVisible(true);
		m_node->attachObject(light);
	}
	
	/* Create the Mesh and the Entity for the middle sphere. */
	{
		/* Create a unique name for the small sphere mesh by adding the id. */
		std::stringstream smOut;
		smOut << "SmallMesh" << id;
		std::string smallMeshId = smOut.str();

		createSphere(smallMeshId, m_radius/10, m_tessellation, m_tessellation);
	
		/* Create a unique name for the small sphere entity by adding the id. */
		std::stringstream seOut;
		seOut << "SmallEnt" << id;
		std::string smallEntityId = seOut.str();

		/* Create the entity from the mesh, and set the material. */
		Entity* sphereMiddle = sceneMgr->createEntity(smallEntityId, smallMeshId);
		sphereMiddle->setMaterialName("Ogre/Earring");

		/* Add the small sphere entity to the list of movable objects, and attach it to the scene node. */
		this->addMovableObject(sphereMiddle);
		m_node->attachObject(sphereMiddle);
	}

	/* Create the Mesh and the Entity for the outer sphere. */
	{
		/* Create a unique name for the big sphere mesh by adding the id. */
		std::stringstream bmOut;
		bmOut << "BigMesh" << id;
		std::string bigMeshId = bmOut.str();

		createSphere(bigMeshId, m_radius, m_tessellation, m_tessellation);
	
		/* Create a unique name for the small sphere entity by adding the id. */
		std::stringstream out5;
		out5 << "BigEntity" << id;
		std::string bigEntityId = out5.str();
		
		/* Create the entity from the mesh, and set the material. */
		Entity* sphereEntity = sceneMgr->createEntity(bigEntityId, bigMeshId);
		sphereEntity->setMaterialName(m_sphereTexName);

		/* Add the big sphere entity to the list of movable objects, and attach it to the scene node. */
		this->addMovableObject(sphereEntity);
		m_node->attachObject(sphereEntity);
	}
}


void PlasmaBall::createSphere(const std::string& strName, const float r, const int nRings, const int nSegments) {

	MeshPtr pSphere = MeshManager::getSingleton().createManual(strName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
	SubMesh *pSphereVertex = pSphere->createSubMesh();

	pSphere->sharedVertexData = new VertexData();
	VertexData* vertexData = pSphere->sharedVertexData;

	// define the vertex format
	VertexDeclaration* vertexDecl = vertexData->vertexDeclaration;
	size_t currOffset = 0;
	// positions
	vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_POSITION);
	currOffset += VertexElement::getTypeSize(VET_FLOAT3);
	// normals
	vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_NORMAL);
	currOffset += VertexElement::getTypeSize(VET_FLOAT3);
	// two dimensional texture coordinates
	vertexDecl->addElement(0, currOffset, VET_FLOAT2, VES_TEXTURE_COORDINATES, 0);
	currOffset += VertexElement::getTypeSize(VET_FLOAT2);

	// allocate the vertex buffer
	vertexData->vertexCount = (nRings + 1) * (nSegments+1);
	HardwareVertexBufferSharedPtr vBuf = HardwareBufferManager::getSingleton().createVertexBuffer(vertexDecl->getVertexSize(0), vertexData->vertexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
	VertexBufferBinding* binding = vertexData->vertexBufferBinding;
	binding->setBinding(0, vBuf);
	float* pVertex = static_cast<float*>(vBuf->lock(HardwareBuffer::HBL_DISCARD));

	// allocate index buffer
	pSphereVertex->indexData->indexCount = 6 * nRings * (nSegments + 1);
	pSphereVertex->indexData->indexBuffer = HardwareBufferManager::getSingleton().createIndexBuffer(HardwareIndexBuffer::IT_16BIT, pSphereVertex->indexData->indexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
	HardwareIndexBufferSharedPtr iBuf = pSphereVertex->indexData->indexBuffer;
	unsigned short* pIndices = static_cast<unsigned short*>(iBuf->lock(HardwareBuffer::HBL_DISCARD));

	float fDeltaRingAngle = (Math::PI / nRings);
	float fDeltaSegAngle = (2 * Math::PI / nSegments);
	unsigned short wVerticeIndex = 0 ;

	// Generate the group of rings for the sphere
	for ( int ring = 0; ring <= nRings; ring++ ) {
		float r0 = r * sinf (ring * fDeltaRingAngle);
		float y0 = r * cosf (ring * fDeltaRingAngle);

		// Generate the group of segments for the current ring
		for ( int seg = 0; seg <= nSegments; seg++ ) {
			float x0 = r0 * sinf(seg * fDeltaSegAngle);
			float z0 = r0 * cosf(seg * fDeltaSegAngle);

			// Add one vertex to the strip which makes up the sphere
			*pVertex++ = x0;
			*pVertex++ = y0;
			*pVertex++ = z0;

			Ogre::Vector3 vNormal = Ogre::Vector3(x0, y0, z0).normalisedCopy();
			*pVertex++ = vNormal.x;
			*pVertex++ = vNormal.y;
			*pVertex++ = vNormal.z;

			*pVertex++ = (float) seg / (float) nSegments;
			*pVertex++ = (float) ring / (float) nRings;

			if (ring != nRings) {
				// each vertex (except the last) has six indices pointing to it
				*pIndices++ = wVerticeIndex + nSegments + 1;
				*pIndices++ = wVerticeIndex;               
				*pIndices++ = wVerticeIndex + nSegments;
				*pIndices++ = wVerticeIndex + nSegments + 1;
				*pIndices++ = wVerticeIndex + 1;
				*pIndices++ = wVerticeIndex;
				wVerticeIndex ++;
			}
		}; // end for seg
	} // end for ring

	// Unlock
	vBuf->unlock();
	iBuf->unlock();
	// Generate face list
	pSphereVertex->useSharedVertices = true;

	// the original code was missing this line:
	pSphere->_setBounds( AxisAlignedBox( Ogre::Vector3(-r, -r, -r), Ogre::Vector3(r, r, r) ), false );
	pSphere->_setBoundingSphereRadius(r);
	// this line makes clear the mesh is loaded (avoids memory leaks)
	pSphere->load();
}